define(
   [
      'dojo/_base/declare',
      'dojo/on',
      'dojo/dom',
      'dojo/_base/lang',
      'dojo/dom-construct'
   ],
   function(declare, on, dom, lang, domConstruct)
   {
      return declare([],
      	 {
      	    classCode: 'InitD3v02',
      	    svg:null,
      	    force:null,
      	    node:null,
      	    link:null,
      	    selected:[],
      	    
      	    constructor:function(args)
      	    {
      	       console.log(this.classCode, "I'm in constructor", this.classCode);
      	    },
      	    
      	    postscript:function()
      	    {
      	      
      	      on(ui.addNode, 'click', lang.hitch(this, function()
      	        {
      	          this.addNewNode();
      	        }));
      	      
      	      on(ui.delNode, 'click', lang.hitch(this, function()
      	        {
      	          this.deleteNode();
      	        }));
      	      
      	      on(ui.addLink, 'click', lang.hitch(this, function()
      	        {
      	          this.addNewLink();
      	        }));
      	      
      	      on(ui.delLink, 'click', lang.hitch(this, function()
      	        {
      	          this.deleteLink();
      	        }));
      	      
      	      on(ui.delBetweenLink, 'click', lang.hitch(this, function()
      	        {
      	          this.deleteBetweenLink();
      	        }));
      	      
      	      this.width = 600;
      	      this.height = 600;
      	      
      	      this.svg = d3.select("#graph_editor_container").append("svg")
      	      .attr("id","svg_graph")
      	      .attr("width", this.width)
      	      .attr("height", this.height)
      	      .attr("class","svg");
      	      
      	      this.svg.append("rect")
      	      .attr("width", this.width)
      	      .attr("height", this.height)
      	      .attr("class","rect");
      	      
      	      this.svg.append("svg:defs").selectAll("marker")
      	      .data(["all"])
      	      .enter()
      	      .append("svg:marker")
      	      .attr("id", String)
      	      .attr("viewBox", "0 -5 10 10")
      	      .attr("refX", 10)
      	      .attr("markerWidth", 6)
      	      .attr("markerHeight", 6)
      	      .attr("orient", "auto")
      	      .append("svg:path")
      	      .attr("stroke","#fff")
      	      .attr("stroke-width",1)
      	      .attr("fill","black")
      	      .attr("d", "M 0,-5 L 10,0 L 0,5z");
      	      
      	      var nodes = [{index:0}];//this.generateNodes(10);
      	      //nodes[0].fixed = true;
      	      nodes[0].x = this.width/2;
      	      nodes[0].y = this.height/2;
      	      var links = [];//this.generateLinks(nodes,{maxWeight:3});
      	      
      	      this.charge = 
      	      {
      	        min: -60,
      	        max: -600,
      	        defaultValue:-600
      	      };
      	      
      	      this.force = d3.layout.force()
      	      .size([this.width, this.height])
      	      .nodes(nodes)
      	      .links(links)
      	      //.linkDistance(200)
      	      .linkDistance(30)
      	      //.charge(-600)
      	      .charge(lang.hitch(this, function(d)
      	        {
      	          var charge = this._chargeDistanceScale(d.weight);
      	          return charge;
      	        }))
      	      .on("tick", lang.hitch(this, this.tick));
      	      
      	      this.links = this.force.links();
      	      this.nodes = this.force.nodes();
      	      
      	      this.path = this.svg.append("g").selectAll(".path"); 
      	      this.updatePath(this.links);
      	      
      	      this.node = this.svg.selectAll(".node");
      	      this.updateNode();
      	       
      	      this.force.start();
      	    },
      	    
      	    tick:function()
      	    {
      	      this.path.attr("d", lang.hitch(this, function(d) 
      	        {
      	          var res = this._getArc(d);
      	          return res;
      	        }));
      	      
      	      this.node.attr("transform", lang.hitch(this, function(d)
      	        { 
      	          return "translate(" + d.x + "," + d.y + ")";
      	        }));
      	      
      	      //this.text.attr("transform", lang.hitch(this, function(d)
      	      //  { 
      	      //    return "translate(" + d.x + "," + d.y + ")";
      	      //  }));
      	      //
      	      //this.selText.attr("transform", lang.hitch(this, function(d)
      	      //  { 
      	      //    return "translate(" + (d.x+10) + "," + (d.y+10) + ")";
      	      //  }));
      	    },
      	    
      	    addNewNode:function()
      	    {
      	      var node =
      	      {
      	        x: this._random(this.width - 10)+5,
      	        y: this._random(this.height - 10)+5,
      	        fixed: false
      	      };
      	      node = this.addNode(node);
      	      
      	      if(this.selected.length > 0)
      	      {
      	        this.selected.forEach(lang.hitch(this,function(targetNode)
      	          {
      	            var target = targetNode.index;
      	            if(target !== node.index)
      	            {
      	              var newLink =
      	              {
      	                source: node.index,
      	                target: target
      	              };
      	              this.addLink(newLink);
      	            }
      	          }));
      	        
      	      }
      	      this.force.start();
      	    },
      	    
      	    addNewLink:function()
      	    {
      	      if(this.selected.length > 1)
      	      {
      	        var source;
      	        var target;
      	        var newLink;
      	        for(var i=1; i<this.selected.length; i++)
      	        {
      	          source = this.selected[i-1].index;
      	          target = this.selected[i].index;
      	          newLink =
      	          {
      	            source: source,
      	            target: target
      	          };
      	          this.addLink(newLink);
      	        }
      	        if(this.selected.length > 2) //link last with first
      	        {
      	          source = this.selected[this.selected.length - 1].index;
      	          target = this.selected[0].index;
      	          newLink =
      	          {
      	            source: source,
      	            target: target
      	          };
      	          this.addLink(newLink);
      	        }
      	        this.force.start();
      	      }
      	    },
      	    
      	    deleteBetweenLink:function()
      	    {
      	      if(this.selected.length > 0)
      	      {
      	        for(var c=0;c<this.nodes.length;c++)
      	        {
      	          var actualNode = this.nodes[c];
      	          if(actualNode.isSelected)
      	          {
      	            this._deleteLinksRelateTo(actualNode,this.selected);
      	          }
      	        }
      	        this.updatePath();
      	        this.links = this.force.links();
      	        this.force.start();
      	      }
      	    },
      	    
      	    deleteLink:function()
      	    {
      	      if(this.selected.length > 0)
      	      {
      	        for(var c=0;c<this.nodes.length;c++)
      	        {
      	          var actualNode = this.nodes[c];
      	          if(actualNode.isSelected)
      	          {
      	            this._deleteLinksRelateTo(actualNode);
      	          }
      	        }
      	        this.updatePath();
      	        this.links = this.force.links();
      	        this.force.start();
      	      }
      	      
      	    },
      	    
      	    deleteNode:function()
      	    {
      	      if(this.selected.length > 0 && this.nodes.length > 0)
      	      {
      	        var c = 0;
      	        do
      	        {
      	          var actualNode = this.nodes[c];
      	          if(actualNode.isSelected)
      	          {
      	            this.nodes.splice(c,1);
      	            this._deleteLinksRelateTo(actualNode);
      	          }
      	          else
      	          {
      	            c++;
      	          }
      	        }while(c<this.nodes.length);
      	        this.updateNode();
      	        this.updatePath();
      	        this.selected = [];
      	        this.nodes = this.force.nodes();
      	        this.links = this.force.links();
      	        
      	        //     this.g.selectAll("svg:polygon").attr("class","polygon");
      	        this.force.start();
      	        this._defaultStyles();
      	      }
      	    },
      	    
      	    _defaultStyles:function()
      	    {
      	      if(this.nodes.length > 0)
      	      {
      	        var ids = "#id-"+this.nodes[0].index+"-sel-text";
      	        for(var i=1; i<this.nodes.length; i++)
      	        {
      	          ids += ",#id-"+this.nodes[i].index+"-sel-text";
      	        }
      	        this.svg.selectAll("g polygon").attr("class","polygon");
      	        var t = this.svg.selectAll(ids); 
      	        t.style("visibility","hidden");
      	      }
      	    },
      	    
      	    _deleteLinksRelateTo:function(/*Node*/refNode,/*Array of index*/involvedNodes)
      	    {
      	      var c = 0;
      	      do
      	      {
      	        var actualLink = this.links[c];
      	        
      	        var guard = involvedNodes ? 
      	        actualLink &&
      	        ((actualLink.source.index === refNode.index && actualLink.target.isSelected) ||
      	          (actualLink.target.index === refNode.index && actualLink.source.isSelected)) :
      	        actualLink &&
      	        (actualLink.source.index === refNode.index ||
      	          actualLink.target.index === refNode.index);
      	        
      	        if(guard)
      	        {
      	          this.links.splice(c,1);
      	        }
      	        else
      	        {
      	          c++;
      	        }
      	      }while(c<this.links.length);
      	    },
      	    
      	    addNode:function(newNode)
      	    {
      	      if(this.nodes.length > 0)
      	      {
      	        var lastNode = this.nodes[this.nodes.length - 1];
      	        newNode.index = lastNode.index + 1;
      	      }
      	      else
      	      {
      	        newNode.index = 0;
      	      }
      	      
      	      this.nodes.push(newNode);
      	      this.updateNode();
      	      this.nodes = this.force.nodes();
      	      return this.nodes[this.nodes.length - 1];
      	    },
      	    
      	    addLink:function(link)
      	    {
      	      if(!this._existLink(link))
      	      {
      	        var newLink =
      	        {
      	          type: 'all'
      	        };
      	        newLink.source = this._findByIndex(this.nodes, link.source);
      	        newLink.target = this._findByIndex(this.nodes, link.target);
      	        
      	        newLink.source.weight = newLink.source.weight || 0;
      	        newLink.source.weight++;
      	        newLink.target.weight = newLink.target.weight || 0;
      	        newLink.target.weight++;
      	        
      	        this.links.push(newLink);
      	        this.updatePath();
      	        this.links = this.force.links();
      	      }
      	    },
      	    
      	    _existLink:function(link)
      	    {
      	      for(var i=0; i<this.links.length; i++)
      	      {
      	        if((this.links[i].source.index === link.source && this.links[i].target.index === link.target) ||
      	           (this.links[i].target.index === link.source && this.links[i].source.index === link.target))
      	        {
      	          return true;
      	        }
      	      }
      	      return false;
      	    },
      	    
      	    updatePath:function()
      	    {
      	      this.path = this.path.data(this.force.links());
      	      this.path.enter()
      	      .append("svg:path")
      	      .attr("class", "link")
      	      .attr("marker-end", function(d) { return "url(#all)"; });
      	      this.path.exit().remove();
      	    },
      	    
      	    updateNode:function()
      	    {
      	      var l = 20;
      	      
      	      this.node = this.node.data(this.force.nodes());
      	      this.node.exit().remove();
      	      
      	      this.g = this.node
      	      .enter()
      	      .append("g")
      	      .call(this.force.drag);
      	      
      	      this.polygon = this.g
      	      .append("svg:polygon")
      	      .attr("class","polygon")
      	      .attr("points",[0, 0, 0, l, l, l, l, 0])
      	      .on('click', function(d,i)
      	        {
      	          console.log("Square clicked",d,i);
      	          d = mo.initD3v02.nodes[i];
      	          d.isSelected = d.isSelected || false;
      	          d.isSelected = !d.isSelected;
      	          d3.select(this)
      	          .attr("class",d.isSelected ? "polygonSelected":"polygon");
      	          
      	          var text_id = "id-"+d.index+"-sel-text";
      	          
      	          if(d.isSelected)
      	          {
      	            var selection_position = mo.initD3v02.selected.push(d);
      	            d3.select("#"+text_id)
      	            .style("visibility","visible")
      	            .text("P:"+selection_position);
      	          }
      	          else
      	          {
      	            var x = mo.initD3v02._findByIndexPosition(mo.initD3v02.selected, d.index);
      	            if(x>=0)
      	            {
      	              mo.initD3v02.selected.splice(x,1);
      	              d3.select("#"+text_id)
      	              .style("visibility","hidden");
      	              
      	              mo.initD3v02._updateSelections();
      	            }
      	          }
      	        });
      	      
      	      this.text = this.g
      	      .append("svg:text")
      	      .attr("class", "nodeLabel")
      	      .text(function(d){return d.index;});
      	      
      	      this.selText = this.g
      	      .append("svg:text")
      	      .attr("id",function(d){return "id-"+d.index+"-sel-text";})
      	      .attr("class","selectedNodeLabel")
      	      .attr("x",10)
      	      .attr("y",15)
      	      .style("visibility","hidden")
      	      .text("P:");
      	      
      	      this.nodes = this.force.nodes();
      	      //this.node.exit().remove();
      	      
      	    },
      	    
      	    _updateSelections:function()
      	    {
      	      this.selected.forEach(lang.hitch(this, function(node,i)
      	        {
      	          var text_id = "id-"+node.index+"-sel-text";
      	          var selection_position = i+1;
      	          d3.select("#"+text_id)
      	          .text("P:"+selection_position);
      	        }));
      	    },
      	    
      	    _findByIndex:function(nodes, index)
      	    {
      	      var n = -1;
      	      for(var i=0; i<nodes.length; i++)
      	      {
      	        if(nodes[i].index === index)
      	        {
      	          n = nodes[i];
      	          return n;
      	        }
      	      }
      	      return n;
      	    },
      	    
      	    _findByIndexPosition:function(nodes, index)
      	    {
      	      var n = -1;
      	      for(var i=0; i<nodes.length; i++)
      	      {
      	        if(nodes[i].index === index)
      	        {
      	          return i;
      	        }
      	      }
      	      return n;
      	    },
      	    
      	    _getAlfa:function(d)
      	    {
      	      // refer to center
      	      var center = {x:this.width/2,y:this.height/2};
      	      var alfa = {x:0,y:0};
      	      
      	      return alfa;
      	    },
      	    
      	    _getPoint:function(d)
      	    {
      	      var res;
      	      return res;
      	    },
      	    
      	    _getArc:function(d)
      	    {
      	      // moveto + The elliptical arc curve commands
      	      var xs = d.source.x;
      	      var ys = d.source.y;
      	      var xt = d.target.x;
      	      var yt = d.target.y;
      	      
      	      var dx = xt - xs;
      	      var dy = yt - ys;
      	      var dr = Math.sqrt(dx * dx + dy * dy);
      	      
      	      //var res = "M" + xs + "," + ys +
      	      //          "a" + dr + "," + dr + " 0 0,1 " + dx + "," + dy;
      	      
      	      //var res = "M" + xs + "," + ys +
      	      //          "c" + 0 + "," + (-50) + " " + dx + "," + (-50) + " " + dx + "," + dy;
      	      //          //"c" + dx+50 + "," + (-50) + " " + (-50) + "," + (-50) + " " + dx + "," + dy;
      	      
      	      var res = "M" + xs + "," + ys +
      	                "l" + dx + "," + dy;
      	      
      	      return res;
      	    },
      	    
      	    generateNodes:function(/*Integer*/size)
      	    {
      	      /*
      	      index - the zero-based index of the node within the nodes array.
      	      x - the x-coordinate of the current node position.
      	      y - the y-coordinate of the current node position.
      	      px - the x-coordinate of the previous node position.
      	      py - the y-coordinate of the previous node position.
      	      fixed - a boolean indicating whether node position is locked.
      	      weight - the node weight; the number of associated links
      	      */
      	      var nodes = [];
      	      
      	      while(size>0)
      	      {
      	        var newNode = 
      	        {
      	          index: size,
      	          x: this._random(this.width),
      	          y: this._random(this.height),
      	          weight: 0
      	        };
      	        nodes.push(newNode);
      	        size--;
      	      }
      	      
      	      return nodes;
      	    },
      	    
      	    generateLinks:function(nodes, options)
      	    {
      	      /*
              source - the source node (an element in nodes).
              target - the target node (an element in nodes).
      	      */
      	      
      	      var links = [];
      	      var map = {};
      	      options = options || {};
      	      
      	      for(var i=0; i<nodes.length; i++)
      	      {
      	        var iNode = nodes[i];
      	        map[iNode.index] = map[iNode.index] || {};
      	        for(var j=0; j<nodes.length; j++)
      	        {
      	          var jNode = nodes[j];
      	          map[jNode.index] = map[jNode.index] || {};
      	          
      	          if(i!==j &&
      	             iNode.weight < options.maxWeight &&
      	             jNode.weight < options.maxWeight &&
      	             !map[jNode.index][iNode.index] &&
      	             this._randomBoolean())
      	          {
      	            //TODO
      	            var newLink =
      	            {
      	              source: iNode,
      	              target: jNode,
      	              type: "all"
      	            };
      	            links.push(newLink);
      	            iNode.weight++;
      	            jNode.weight++;
      	            map[iNode.index][jNode.index] = jNode;
      	          }
      	        }
      	      }
      	      
      	      var nodeSet = this._copy(nodes);
      	      for(i=0; i<nodes.length; i++)
      	      {
      	        iNode = nodes[i];
      	        if(iNode.weight === 0)
      	        {
      	          console.log('Node '+iNode.index+' needs a link', iNode);
      	          var index = this._random(nodeSet.length);
      	          var validNode = nodes[index];
      	          var MAX_TRY = 2000;
      	          
      	          while((validNode.index === iNode.index ||
      	                 validNode.weight >= options.maxWeight) && 
      	                MAX_TRY > 0)
      	          {
      	            nodeSet = this._remove(nodeSet,index);
      	            index = this._random(nodeSet.length);
      	            validNode = nodes[index];
      	            MAX_TRY--;
      	          }
      	          
      	          newLink =
      	          {
      	            source: iNode,
      	            target: validNode,
      	            type: "all"
      	          };
      	          console.log('Link added', newLink);
      	          links.push(newLink);
      	          iNode.weight++;
      	          validNode.weight++;
      	          nodeSet = this._copy(nodes);
      	        }
      	      }
      	      
      	      
      	      return links;
      	    },
      	    
      	    _copy:function(array)
      	    {
      	      var copy = [];
      	      
      	      for(var i=0;i<array.length;i++)
      	      {
      	        copy.push(array[i]);
      	      }
      	      
      	      return copy;
      	    },
      	    
      	    _remove:function(array, index)
      	    {
      	      array.splice(index,1);
      	      return array;
      	    },
      	    
      	    _randomBoolean:function()
      	    {
      	      return Math.random() > 0.5;
      	    },
      	    
      	    _random:function(n)
      	    {
      	      return Math.floor(Math.random()*n); 
      	    },
      	    
      	    _chargeDistanceScale:function(weight)
      	    {
      	      this.domainOnlyScale = this.domainOnlyScale || d3.scale
      	      .linear()
      	      .domain([1,5])
      	      .range([this.charge.min,this.charge.max]);
      	      weight = weight>0 ? weight : 1;
      	      weight = weight>5 ? 5 : weight;
      	      return this.domainOnlyScale(weight);
      	    },
      	    
      	    endOfLib:null
      	 });
   });
